package com.nulldev.util.security.encryptAPI_rw.impls.des;

import static com.nulldev.util.security.encryptAPI_rw.utils.CipherPaddingUtil.__contains_padding;
import static com.nulldev.util.security.encryptAPI_rw.utils.CipherPaddingUtil.__pad_array;
import static com.nulldev.util.security.encryptAPI_rw.utils.CipherPaddingUtil.__unpadded_length;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.data.Variables;
import com.nulldev.util.security.encryptAPI_rw.api.ICipherType;
import com.nulldev.util.security.encryptAPI_rw.api.IStandardBlockingCipher;
import com.nulldev.util.security.encryptAPI_rw.api.IStandardStreamingCipher;

public class DESCipher implements IStandardBlockingCipher, IStandardStreamingCipher {

	private static final String[] CIPHER_NAMES =
		{ "DES" };

	@Override
	public String getEncryptorID() {
		return "ecr_v3.internal.des";
	}

	@Override
	public String[] getCipherNames() {
		return CIPHER_NAMES;
	}

	@Override
	public long encrypt(final InputStream is, final OutputStream os, final byte[] privateKey) {
		Variables.requireNonNull(is, "is");
		Variables.requireNonNull(os, "os");
		Variables.requireNonNull(privateKey, "privateKey");
		long count = 0;
		final int BLOCK_SIZE = 8;
		final byte[] b = new byte[BLOCK_SIZE];
		try {
			while ((count += (is.read(b, 0, BLOCK_SIZE))) != -1) {
				InternalDESCipher.encryptBlock(b, 0, b, 0, privateKey);
				os.write(b, 0, BLOCK_SIZE);
			}
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
		return count;
	}

	@Override
	public long decrypt(final InputStream is, final OutputStream os, final byte[] privateKey) {
		Variables.requireNonNull(is, "is");
		Variables.requireNonNull(os, "os");
		Variables.requireNonNull(privateKey, "privateKey");
		long count = 0;
		final int BLOCK_SIZE = 8;
		final byte[] b = new byte[BLOCK_SIZE];
		try {
			while ((count += (is.read(b, 0, BLOCK_SIZE))) != -1) {
				InternalDESCipher.decryptBlock(b, 0, b, 0, privateKey);
				os.write(b, 0, BLOCK_SIZE);
			}
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
		return count;
	}

	@Override
	public byte[] encrypt(final byte[] data, final byte[] key) {
		Variables.requireNonNull(key, "key");
		final byte[] out = new byte[MathUtil.padToInterval(Variables.requireNonNullObject(data, "data").length, 8)];
		final boolean needs_padding = out.length != data.length;
		System.arraycopy(data, 0, out, 0, data.length);
		if (needs_padding)
			__pad_array(out, data.length, (byte) '\4');
		for (int i = 0; i < data.length; i += 8)
			InternalDESCipher.encryptBlock(out, i, out, i, key);
		return out;
	}

	@Override
	public byte[] decrypt(byte[] data, byte[] key) {
		Variables.requireNonNull(key, "key");
		Variables.requireNonNull(data, "data");
		final byte[] out = new byte[data.length];
		for (int i = 0; i < data.length; i += 8)
			InternalDESCipher.decryptBlock(data, i, out, i, key);
		final boolean was_padded = __contains_padding(out);
		if (was_padded) {
			final int true_length = __unpadded_length(out);
			return Arrays.copyOfRange(out, 0, true_length);
		} else {
			return out;
		}
	}

	@Override
	public ICipherType getCipherType() {
		return ICipherType.PRIVATE_KEY;
	}
}
